﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Xml.Serialization;
using PodpisyElektorniczne.Interfaces;
using BIG = System.Numerics.BigInteger;


namespace PodpisyElektorniczne.Classes
{
    public class StepOutRingSignature
    {
        public bool isPubPrivGen
        {
            get { return (_asymKeyPair.GetPrivateKey() != null && _asymKeyPair.Y != null); }
            protected set{}
        }
        public string Sign
        {
            get
            {   
                if(_sign != null)
                    return _sign.ToString();
                return string.Empty;
            }
            protected set { }
        }
        public Signature _sign; 
        private BIG _msg;
        private BIG _dashedW;
        private BIG _dashedY;
        private BIG _dashedYw; 

        private List<BIG> _keysY;
        private List<BIG> _saltW;
        protected BIG _Wj { get; set; }

        //general settings
        private readonly SchemaSetts _schemaSetts; 
        private readonly UserKeys _asymKeyPair;   
        private readonly KeyPreferences _settsP;     
        private Seqdl _seqdl;
        private readonly List<Skdl> _skdl;
        private string _keyPath ;
        private uint RingSize { get; set; }

        public StepOutRingSignature(int size, string msg)
            : this(msg)
        {
            RingSize = (uint)(new Random()).Next(size - 1);
        }

        public StepOutRingSignature()
        {
            try
            {

                _keyPath = string.Format("{0}\\{1}\\{2}", Settings.MainDirectoryPath, Form1.Log.Usser.Login,
                                             Settings.KeyPairFilename);
                _schemaSetts = (new SchemaSetts(Program.Log.Usser.Login)).XmlDeserializeSchemaProperties();
                _settsP = KeyPreferences.ObjDeserializer();
                _seqdl = new Seqdl();
                _skdl = new List<Skdl>();

                var kFactory = new UserKeyFactory();
                kFactory.Deserialize(_keyPath);

                RingSize = (uint)_settsP.NoMembers;

                _asymKeyPair = kFactory.KeyPair;
                if(_asymKeyPair == null)
                {
                    isPubPrivGen = false; 
                    throw new Exception(@"There is a problem with your Key values. Check them and try again.");
                }
            }
            catch(Exception)
            {
                throw;
            }
        }

        public StepOutRingSignature(string msg)
        {
            try
            {
                _keyPath = string.Format("{0}\\{1}\\{2}", Settings.MainDirectoryPath, Form1.Log.Usser.Login,
                                             Settings.KeyPairFilename);
                _msg = new BIG(Encoding.UTF8.GetBytes(msg));
                _seqdl = new Seqdl();
                _keysY = new List<BIG>();
                _saltW = new List<BIG>();
                _dashedY = new BIG();
                
                _schemaSetts = (new SchemaSetts(Program.Log.Usser.Login)).XmlDeserializeSchemaProperties();
                _settsP = KeyPreferences.ObjDeserializer();
                _seqdl = new Seqdl();
                _skdl = new List<Skdl>();
                _asymKeyPair = new UserKeys();

                var kFactory = new UserKeyFactory();
                kFactory.Deserialize(_keyPath);

                _asymKeyPair = kFactory.KeyPair;
            }
            catch(Exception)
            {
                throw;
            }
        }
        #region IO for signature
        public bool StoreSRSignature(string path)
        {
            var result = false;
            try
            {
                if (_sign.ObjSerialization(path)) result = true;
                    return result;
            }catch(Exception ex)
            {
                throw new Exception(@"Stored key has invalid format. Create signature first." + ex.Message);
            }
        }

        public static StepOutRingSignature GetStoredSRSignature(string path)
        {
            var rawSign =  Signature.ObjDeserializerStat(path,true);
            return new StepOutRingSignature
                       {
                           _msg = BIG.Parse(rawSign.Msg),
                           _dashedW = rawSign.DashedW,
                           _dashedY = rawSign.DashedY,
                           _saltW = rawSign.SaltW,
                           _keysY = rawSign.PublicKeysY,
                           _seqdl = new Seqdl
                                        {
                                            VectorC = rawSign.VectorC,
                                            VectorS = rawSign.VectorS
                                        },
                            _sign = rawSign
                            
                           
                       };


            
        }
        #endregion
        
        //this required because there is no method which has get BI arguments type: a^b
        public static BIG Pow(BIG a, BIG b)
        {
            //to implement
            var result = new BIG(1);
            int parseOut=0;
            if(Int32.TryParse(b.ToString("R"), out parseOut))
            {
                return BIG.Pow(a, parseOut);
            }
            
            
            var loopIterator = (int) BIG.Divide(b, BIG.Parse(Int32.MaxValue.ToString()));
            var rest = (int)(b%BIG.Parse(Int32.MaxValue.ToString()));

            result = BIG.Pow(a, rest);
            for(var r= loopIterator; r>0; r--)
            {
                result *= BIG.Pow(a, Int32.MaxValue);
            }
            return result;
        }


        

        public bool SRSignature()
        {
            try
            {
                //prepare salt vector W: w_j will be the last element
                _keysY = (null == _keysY || 0 == _keysY.Count) ? PreparePublicKeys() : _keysY;
                if(!_keysY.Exists(ij => ij == _asymKeyPair.Y))_keysY.Add(_asymKeyPair.Y);
                _saltW = PrepareSaltData();
                //compute dashed values: w, y, y_w = y*w
                _dashedW = BIG.ModPow(_schemaSetts.DashedGeneratorG, _schemaSetts.SecretRj, _schemaSetts.SubOrderQ);
                _dashedY = BIG.ModPow(_schemaSetts.DashedGeneratorG, _asymKeyPair.GetPrivateKey(), _schemaSetts.SubOrderQ);
                _dashedYw = BIG.Multiply(_dashedY, _dashedW)%_schemaSetts.SubOrderQ;
                
                
                //compute seqdl
                _seqdl.SeqdlSignature(_schemaSetts, _asymKeyPair, _settsP, _dashedYw, _keysY, _saltW, _msg);

                //computing skdl - proof of knowledge of w_i, r and other stuff
                var factory = new SkdlFactory(_schemaSetts.SubOrderQ, _settsP.LengthOrderZq);
                foreach(BIG w_i in _saltW)
                {
                    _skdl.Add( factory.BuildSignature(_schemaSetts.GeneratorG, w_i, _msg));                    
                }
                                                 
                _sign = new Signature
                            {
                                DashedY = _dashedY,
                                DashedW = _dashedW,
                                DashedG = _schemaSetts.DashedGeneratorG,
                                G = _schemaSetts.GeneratorG,
                                SaltW = _saltW,
                                PublicKeysY = _keysY,
                                VectorC = _seqdl.VectorC,
                                VectorS = _seqdl.VectorS,
                                Skdl =  _skdl,
                                Msg = _msg.ToString("R")
                            };
                
                return true;
            }catch(Exception ex)
            {
                throw ex;
            }
        }
        //to find interesection of two setts of keys where rsult will be Yj
        public static List<BIG> PreparePubKeysIntersect(IList<BIG> chosenKeys, BIG yj, int noMem)
        {
            var kFactory = new UserKeyFactory();
            var dicProps = Directory.GetDirectories(Settings.MainDirectoryPath);
            var ls = new List<UserKeys>();

            foreach(var dictionaryProp in dicProps)
            {
                kFactory.Deserialize(string.Format("{0}\\{1}", dictionaryProp, Settings.KeyPairFilename));
                ls.Add(kFactory.KeyPair);
            }

            var candidates = new List<BIG>();
            
            foreach(var item in ls)
            {
                var t = (from it in chosenKeys
                        where it == item.Y
                        select it) as UserKeys;
                if(t!=null)
                    candidates.Add(t.Y);
            }
                
            
            var res = candidates.Count >= noMem  ? candidates.GetRange(0, noMem) : candidates;
            
            return res;
        }

        
        
        private List<BIG> PreparePublicKeys()
        {
            var publicKeys = new List<BIG>();
            var keyFolders = (new DirectoryInfo(Settings.MainDirectoryPath)).GetDirectories("*");
            var kfactory = new UserKeyFactory();
            var end=0;
            var t = 0;
            int members;
            try
            {
                while (end != RingSize && t < keyFolders.Count())
                {
                    if (keyFolders[t].Name != Program.Log.Usser.Login)
                    {
                        kfactory.Deserialize(string.Format("{0}\\{1}\\{2}",Settings.MainDirectoryPath, keyFolders[t].Name,Settings.KeyPairFilename));
                        if (kfactory.KeyPair.Y != -1)
                        {
                            publicKeys.Add(kfactory.KeyPair.Y);
                            end++;
                        }
                    }
                    t++;
                }
                return publicKeys;
            }catch(Exception)
            {
                throw;
            }
        }


        public List<BIG> PrepareSaltData()
        {
            var vectorW = new List<BIG>();
            var wJ = BIG.ModPow(_schemaSetts.GeneratorG, _schemaSetts.SecretRj, _schemaSetts.SubOrderQ) ; //Pow(_schemaSetts.GeneratorG, _schemaSetts.SecretRj);
            var k = 0;
            //if(_settsP.NoMembers+1 == 2)
            //{
                //vectorW.Add(wJ);
               // return vectorW;
            //}
            if (_keysY.Count > 1)
            {
                while (k < RingSize - 1)//_keysY.Count-1)
                {
                    var rng = new RNGCryptoServiceProvider();
                    var rngBytes = new byte[_settsP.LengthOrderZp];
                    rng.GetBytes(rngBytes);

                    var rI = new BIG(rngBytes);
                    var wI = BIG.ModPow(_schemaSetts.GeneratorG, rI < 0 ? rI * (-1) : rI, _schemaSetts.SubOrderQ);
                    if (_keysY[k] * wI != _keysY[_keysY.Count - 1] * wJ)
                    {
                        vectorW.Add(wI);
                        k++;
                    }
                }
            }
            //the last value on the list, it will be w_j which is directly connected with the signer himself
            //it's important to remember which value will be validated
            
            vectorW.Add(wJ);
            return vectorW;
        }

        public bool VerificationSrs()
        {
            return _seqdl.VeryfieUj(_schemaSetts.SecretRj, _asymKeyPair.GetPrivateKey(), _schemaSetts.GeneratorG,
                                             BIG.ModPow(_schemaSetts.GeneratorG, _schemaSetts.SecretR,
                                                       _schemaSetts.SubOrderQ), _schemaSetts.SubOrderQ, BIG.ModPow(_schemaSetts.DashedGeneratorG, _schemaSetts.SecretR,
                                                       _schemaSetts.SubOrderQ), _schemaSetts.DashedGeneratorG);
        }

        public bool VerificationSrs(Signature sign, BIG order)
        {
            var t = new List<BIG>();
            var u = new List<BIG>();
            var dashedYw = (sign.DashedY*sign.DashedW) %order;

            var sumOfVectorC = sign.VectorC.Aggregate<BIG, BIG>(0,(current, c) => current + c);

            for (int i = 0; i < sign.VectorC.Count; i++)
            {
                t.Add((BIG.ModPow(sign.DashedG, sign.VectorS[i], order) *
                               BIG.ModPow(dashedYw, sign.VectorC[i], order)) % order);
                u.Add((BIG.ModPow(sign.G, sign.VectorS[i], order) *
                               BIG.ModPow(sign.PublicKeysY[i] * sign.SaltW[i], sign.VectorC[i], order)) % order);
            }
                   
            
            var toHash = string.Empty;
            toHash += sign.DashedG.ToString() + sign.G.ToString() + dashedYw;

            toHash = sign.PublicKeysY.Aggregate(toHash, (current, p) => current.ToString() + p.ToString()); // czy tu ma być plus ? czy ma być agregate
            toHash = sign.SaltW.Aggregate(toHash, (current, p) => current.ToString() + p.ToString());

            for (var i = 0; i < t.Count; i++)
                toHash += string.Format("{0}{1}", t[i], u[i]);

            toHash += sign.Msg;
            var source = Encoding.ASCII.GetBytes(toHash);
            var md5Hash = new MD5CryptoServiceProvider().ComputeHash(source);

            return sumOfVectorC == (new BIG(md5Hash));

        }

        public bool ConfessionSrs()
        {
            if (!VerificationSrs()) return false;
            var fakeSrs = new StepOutRingSignature(@"I've never seen this message before. I'm not the signer.")
                               {
                                   _keysY = PreparePubKeysIntersect(_keysY, _keysY.Last(),_settsP.NoMembers)
                               };
            if(!fakeSrs._keysY.Exists(ij => ij == fakeSrs._asymKeyPair.Y))fakeSrs._keysY.Add(fakeSrs._asymKeyPair.Y);
            if(fakeSrs.SRSignature())
            {
                if(fakeSrs.VerificationSrs() && VerificationSrs() && fakeSrs._keysY.Intersect(_keysY).ToList().Last() == _keysY.Last())
                {
                    return true;
                }
            }
            return false;
        }

        public bool SetpOutSrs()
        {
            var fakeSrsPrim = new StepOutRingSignature(@"I've never seen this message before. I'm not the signer.");
            var kFactory = new UserKeyFactory();
            fakeSrsPrim.SRSignature();
            var fakeSrsDoubPrim = new StepOutRingSignature(@"I've never seen this message before. I'm not the signer.")
                                      {
                                          _keysY =
                                              PreparePubKeysIntersect(fakeSrsPrim._keysY, fakeSrsPrim._keysY.Last(), _settsP.NoMembers)
                                      };
            if (fakeSrsDoubPrim.SRSignature())
            {
                if (VerificationSrs() && fakeSrsPrim.VerificationSrs() && fakeSrsDoubPrim.VerificationSrs())
                {
                    kFactory.Deserialize(_keyPath);
                    if (fakeSrsPrim._keysY.Intersect(fakeSrsDoubPrim._keysY).FirstOrDefault() == kFactory.KeyPair.Y &&
                        (_dashedYw != fakeSrsDoubPrim._dashedYw))
                    {
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
    }
}
